// 测试hmdfs跨网络通信，网络使用socket通信

#include <arpa/inet.h>
#include <fcntl.h>
#include <linux/tcp.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <list>
#include <thread>
#include <sstream>
#include <stdio.h>
#include <string>

using namespace std;

int port = 6666;
const int GROOTFS_KEY_SIZE = 32;
const int GROOTFS_ACCOUNT_HASH_MAX_LEN = 21;
char *ipaddr;
const int GROOTFS_CID_SIZE = 64;
static const std::string CTRL_PATH_BASE = "/sys/fs/hmdfs";
static std::string g_ctrlPath {};
bool g_connectQuit = false;
#define MASTER_KEY "01234567890123456789012345678901"
int gCount = 2;

enum {
    CMD_UPDATE_SOCKET = 0,
    CMD_UPDATE_DEVSL,
    CMD_OFFLINE,
    CMD_OFF_LINE_ALL,
    CMD_CNT
};

enum {
    SOCKET_STAT_ACCEPT = 0,
    SOCKET_STAT_OPEN
};

struct UpdateSocketParam {
    int32_t cmd;
    int32_t newfd;
    uint32_t devsl;
    uint8_t status;
    uint8_t masterkey[GROOTFS_KEY_SIZE];
    char cid[GROOTFS_CID_SIZE];
} __attribute__((packed));

struct UpdateDslParam {
    int32_t cmd;
    uint32_t devsl;
    char cid[GROOTFS_CID_SIZE];
} __attribute__((packed));

struct OfflineParam {
    int32_t cmd;
} __attribute__((packed));

template<typename T>
void setCmd(T &cmd) {
    int file = open(g_ctrlPath.c_str(), O_RDWR);
    if (file < 0) {
        cout << "setCmd Open node file error." << endl;
        return;
    }
    int err = write(file, &cmd, sizeof(T));
    if (err < 0) {
        cout << "setCmd write to file error." << endl;
    }
    close(file);
    return;
}

int socketConnected(int sock) {
    if (sock < 0) {
        return 0;
    }
    struct tcp_info info;
    socklen_t len = sizeof(info);
    getsockopt(sock, IPPROTO_TCP, TCP_INFO, &info, &len);
    return info.tcpi_state;
}

// 服务端启监听
void serverListen() {
    list<int> clientList;
    const int LISTEN_NUM = 10;
    const int ACCEPT_TIMEOUT = 6;
    struct sockaddr_in serverAddr;
    struct sockaddr_in clientAddr;
    socklen_t addrLen = sizeof(clientAddr);
    int reuse = 1;

    int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (serverSocket < 0) {
        perror("socket");
        return;
    }

    // bind前设置重用这个socket地址，即多个socket可以公用这个地址
    if (setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
        perror("setsockopt SO_REUSEADDR");
        close(serverSocket);
        return;
    }

    // bind前设置重用这个socket的端口，即多个socket可以公用这个port
    if (setsockopt(serverSocket, SOL_SOCKET, SO_REUSEPORT, (const void*)&reuse, sizeof(reuse)) < 0) {
        perror("setsockopt SO_REUSEPORT");
        close(serverSocket);
        return;
    }

    bzero(&serverAddr, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(port);
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    if (::bind(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {
        perror("connect");
        close(serverSocket);
        return;
    }

    // 开始监听
    if (listen(serverSocket, LISTEN_NUM) < 0) {
        perror("listen");
        close(serverSocket);
        return;
    }

    struct timeval timeout = {ACCEPT_TIMEOUT, 0};

    while (!g_connectQuit) {
        // 设置recv超时时间，因为默认是阻塞模式，防止长时间阻塞
        if (setsockopt(serverSocket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) {
            perror("set timeout");
            close(serverSocket);
            return;
        }

        int acclient = accept(serverSocket, (struct sockaddr *)&clientAddr, &addrLen);
        if (acclient >= 0) {
            cout << "IP is: " << inet_ntoa(clientAddr.sin_addr) << endl;
            cout << "Port is: " << htons(clientAddr.sin_port) << endl;
            clientList.push_back(acclient);

            UpdateSocketParam cmd = {
                .cmd = CMD_UPDATE_SOCKET,
                .newfd = acclient,
                .devsl = 0,
                .status = SOCKET_STAT_ACCEPT,
            };
            memcpy(cmd.masterkey, MASTER_KEY, GROOTFS_KEY_SIZE);
            string cidTmp = "cid" + std::to_string(clientList.size()+1);
            memcpy(cmd.cid, cidTmp.c_str(), GROOTFS_CID_SIZE);
            setCmd(cmd);

            UpdateDslParam cmd1 = {
                .cmd = CMD_UPDATE_DEVSL,
                .devsl = 2,
            };
            memcpy(cmd1.cid, cidTmp.c_str(), GROOTFS_CID_SIZE);
            setCmd(cmd1);
        }

        for (auto it = clientList.begin(); it != clientList.end(); ++it) {
            if (socketConnected(*it) != 1) {
                cout << "disconnect is: " << *it << endl;
                OfflineParam cmd = {
                    .cmd = CMD_OFF_LINE_ALL,
                };
                setCmd(cmd);

                close(*it);
                clientList.erase(it); // list删除方便
                break; // ?
            }
        }
    }
    for (auto it = clientList.begin(); it != clientList.end(); ++it) {
        cout << "disconnect is: " << *it << endl;
        OfflineParam cmd = {
            .cmd = CMD_OFF_LINE_ALL,
        };
        setCmd(cmd);
        close(*it);
        clientList.erase(it);
    }

    close(serverSocket);
}

void clientConnect() {
    struct sockaddr_in serverAddr;
    const int SEND_BUF_LEN = 200;
    char sendBuf[SEND_BUF_LEN];
    int clientSocket;

    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(port);
    serverAddr.sin_addr.s_addr = inet_addr(ipaddr);

    while (true) {
        clientSocket = socket(AF_INET, SOCK_STREAM, 0);
        if (clientSocket < 0) {
            perror("create client socket");
            return;
        }

        cout << "begin to connect..." << endl;

        if (connect(clientSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {
            cout << "client connect error." << endl;
            close(clientSocket);
            sleep(10);
            continue;
        }

        cout << "connected to " << inet_ntoa(serverAddr.sin_addr);
        cout << ":" << htons(serverAddr.sin_port) << endl;

        UpdateSocketParam cmd = {
            .cmd = CMD_UPDATE_SOCKET,
            .newfd = clientSocket,
            .devsl = 0,
            .status = SOCKET_STAT_OPEN,
        };
        memcpy(cmd.masterkey, MASTER_KEY, GROOTFS_KEY_SIZE);
        memcpy(cmd.cid, CID, GROOTFS_CID_SIZE);
        setCmd(cmd);

        UpdateDslParam cmd1 = {
            .cmd = CMD_UPDATE_DEVSL,
            .devsl = 4,
        };
        memcpy(cmd1.cid, CID, GROOTFS_CID_SIZE);
        setCmd(cmd1);

        while (true) {
            sleep(5);
            if (socketConnected(clientSocket) != 1) {
                OfflineParam cmd = {
                    .cmd = CMD_OFF_LINE_ALL,
                };
                setCmd(cmd);
                close(clientSocket);
                break;
            }
        }
    }
}

static uint64_t pathHash(const char *path, int len) {
    uint64_t res = 0;
    const char *kp = path;
    while (*kp) {
        res = (res << 5) - res + (uint64_t) * kp++;
    }
    return res;
}

int main(int argc, char* argv[]) {
    const int ARG_NUM = 3;
    if (argc < 3) {
        cout << "Please input source ./xxxx path -s/-c ip" << endl;
        return -1;
    }

    uint64_t hash = pathHash(argv[1], strlen(argv[1]));
    std::stringstream path;
    path << CTRL_PATH_BASE << "/" << hash << "/cmd";
    g_ctrlPath = path.str();
    cout << "ctrl path" << g_ctrlPath << endl;
    if (strcmp(argv[2], "-s") == 0) {
        serverListen();
    } else if (strcmp(argv[2], "-c") == 0) {
        if (argc != ARG_NUM + 1) {
            cout << "Client input param num is invalid." << endl;
            return -1;
        }
        ipaddr = argv[3];
        clientConnect();
    } else {
        cout << "invalid input param." << endl;
    }

    return 0;
}